package com.cgt.xiaotao.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cgt.common.domain.dto.PageDTO;
import com.cgt.common.exception.BadRequestException;
import com.cgt.common.utils.*;
import com.cgt.xiaotao.promotion.constants.PromotionConstants;
import com.cgt.xiaotao.promotion.domain.dto.CouponFormDTO;
import com.cgt.xiaotao.promotion.domain.dto.CouponIssueFormDTO;
import com.cgt.xiaotao.promotion.domain.po.Coupon;
import com.cgt.xiaotao.promotion.domain.po.CouponScope;
import com.cgt.xiaotao.promotion.domain.po.UserCoupon;
import com.cgt.xiaotao.promotion.domain.query.CouponQuery;
import com.cgt.xiaotao.promotion.domain.vo.CouponDetailVO;
import com.cgt.xiaotao.promotion.domain.vo.CouponPageVO;
import com.cgt.xiaotao.promotion.domain.vo.CouponVO;
import com.cgt.xiaotao.promotion.enums.ObtainType;
import com.cgt.xiaotao.promotion.enums.UserCouponStatus;
import com.cgt.xiaotao.promotion.mapper.CouponMapper;
import com.cgt.xiaotao.promotion.enums.CouponStatus;
import com.cgt.xiaotao.promotion.service.ICouponScopeService;
import com.cgt.xiaotao.promotion.service.ICouponService;
import com.cgt.xiaotao.promotion.service.IExchangeCodeService;
import com.cgt.xiaotao.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.cgt.xiaotao.promotion.enums.CouponStatus.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;
    private final StringRedisTemplate redisTemplate;
    private final IExchangeCodeService exchangeCodeService;
    private final IUserCouponService userCouponService;

    @Override
    public void saveCoupon(CouponFormDTO dto) {
        //1.dto转vo保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);

        //2.判断是否限定了范围 dto.specific为false代表无限定
        if (!dto.getSpecific()) {
            return;//没有限定优惠券的使用
        }

        //3.如果为true，需要校验dto.scopes是否为空
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("分类id不能为空");
        }

        //4.保存优惠券范围,批量新增
        List<CouponScope> csList = scopes
                .stream()
                .map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(csList);

    }

    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        //1.分页条件查询优惠券表
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getDiscountType() != null && query.getDiscountType() != 0, Coupon::getDiscountType, query.getDiscountType())
                .eq(query.getStatus() != null && query.getStatus() != 0, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        System.out.println("total: " + page.getTotal());
        System.out.println("pages: " + page.getPages());
        //2.封装vo返回
        List<CouponPageVO> voList = BeanUtils.copyList(records, CouponPageVO.class);

        return PageDTO.of(page, voList);
    }

    @Override
    public void updateCoupon(Long id, CouponFormDTO dto) {
        //1.查询优惠券判断是否是待发放
        Coupon coupon = this.getById(id);
        if (coupon.getStatus() != CouponStatus.DRAFT) {
            throw new BadRequestException("优惠券状态不是待发放，不能修改");
        }
        //2.dto转vo保存优惠券 coupon表
        Coupon updateCoupon = BeanUtils.copyBean(dto, Coupon.class);
        this.updateById(updateCoupon);
    }

    @Override
    public void deleteById(Long id, CouponFormDTO dto) {
        //1.查询优惠券判断是否是待发放
        Coupon coupon = this.getById(id);
        if (coupon.getStatus() != CouponStatus.DRAFT) {
            throw new BadRequestException("优惠券状态不是待发放，不能删除");
        }
        //2.dto转vo删除优惠券 coupon表
        this.removeById(id);
    }

    @Override
    public CouponDetailVO queryCouponById(Long id) {
        // 1.查询优惠券
        Coupon coupon = getById(id);
        // 2.转换VO
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        // 3.确保范围列表为空（因为商品项目中优惠券没有限定范围）
        if (vo != null) {
            vo.setScopes(Collections.emptyList());
        }
        // 4.返回结果
        return vo;
    }

    //发放优惠券
    @Override
    @Transactional
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        //1.校验参数
        if (id == null || !id.equals(dto.getId())) {
            throw new BadRequestException("参数非法");
        }
        //2.校验优惠券id是否存在
        Coupon coupon = this.getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        //3.校验优惠券状态
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE) {
            throw new BadRequestException("优惠券状态非法");
        }
        //判断是否立即发放
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBeginIssue = issueBeginTime == null || issueBeginTime.isAfter(now);

        //4.拷贝属性到po
        Coupon c = BeanUtils.copyBean(dto, Coupon.class);
        c.setId(id); // 确保ID被正确设置
        //更新状态
        if (isBeginIssue) {
            //开始发放
            c.setIssueBeginTime(dto.getIssueBeginTime() == null ? now : dto.getIssueBeginTime());
            c.setStatus(ISSUING);
        } else {
            //定时发放
            c.setStatus(UN_ISSUE);
        }
        boolean updateResult = this.updateById(c);
        if (!updateResult) {
            throw new RuntimeException("更新优惠券信息失败");
        }

        //如果优惠券是立刻发放，将信息存入redis id 领券开始时间 结束时间 发行数量 限定数量
        if (isBeginIssue) {
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId();
            try {
                redisTemplate.opsForHash().put(key, "issueBeginTime", String.valueOf(DateUtils.toEpochMilli(c.getIssueBeginTime())));
                redisTemplate.opsForHash().put(key, "issueEndTime", String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
                redisTemplate.opsForHash().put(key, "totalNum", String.valueOf(coupon.getTotalNum()));
                redisTemplate.opsForHash().put(key, "userLimit", String.valueOf(coupon.getUserLimit()));
            } catch (Exception e) {
                log.error("将优惠券信息存入Redis失败", e);
                throw new RuntimeException("将优惠券信息存入Redis失败", e);
            }
        }

        //6.如果领取优惠券是指定发放且状态是待发放，需要生成兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            try {
                coupon.setIssueEndTime(dto.getIssueEndTime());//兑换码截至时间
                coupon.setTermEndTime(dto.getIssueEndTime());//兑换码截至时间
                exchangeCodeService.asyncGenerateExchangeCode(coupon);//异步生成兑换码
            } catch (Exception e) {
                log.error("生成兑换码失败", e);
                throw new RuntimeException("生成兑换码失败", e);
            }
        }
    }

    private void cacheCouponInfo(Coupon coupon) {
        // 1.组织数据
        Map<String, String> map = new HashMap<>(4);
        map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueBeginTime())));
        map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
        map.put("totalNum", String.valueOf(coupon.getTotalNum()));
        map.put("userLimit", String.valueOf(coupon.getUserLimit()));
        // 2.写缓存
        redisTemplate.opsForHash().putAll(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), map);
    }

    @Override
    @Transactional
    public void pauseIssue(Long id) {
        // 1.查询旧优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }

        // 2.当前券状态必须是未开始或进行中
        CouponStatus status = coupon.getStatus();
        if (status != UN_ISSUE && status != ISSUING) {
            // 状态错误，直接结束
            return;
        }

        // 3.更新状态
        boolean success = lambdaUpdate()
                .set(Coupon::getStatus, PAUSE)
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, UN_ISSUE, ISSUING)
                .update();
        if (!success) {
            // 可能是重复更新，结束
            log.error("重复暂停优惠券");
        }

        // 4.删除缓存
        redisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);
    }

    @Override
    public List<CouponVO> queryIssuingCoupons() {
        //1.查询db，发放中并且手动领取
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }
        // 2.统计当前用户已经领取的优惠券的信息
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        // 2.1.查询当前用户已经领取的优惠券的数据
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        // 2.2.统计当前用户对优惠券的已经领取数量
        Map<Long, Long> issuedMap = userCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 2.3.统计当前用户对优惠券的已经领取并且未使用的数量
        Map<Long, Long> unusedMap = userCoupons.stream()
                .filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        //3. po转vo
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon coupon : coupons) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            //是否可以领取：已经被领取的数量 < 优惠券总数量 && 当前用户已经领取的数量 < 每人限领数量
            Long issuedNum = issuedMap.getOrDefault(coupon.getId(), 0L);
            boolean available = coupon.getIssueNum() < coupon.getTotalNum() && issuedNum < coupon.getUserLimit();
            vo.setAvailable(available);
            boolean received = unusedMap.getOrDefault(coupon.getId(), 0L) > 0;
            vo.setReceived(received);//是否可以使用
            voList.add(vo);
        }
        return voList;
    }
}
